---
title: Deep Linking
---

Before starting, you should reference the extensive docs from React Navigation for setting up Deep Linking:

- [React Navigation Deep Linking Guide](https://reactnavigation.org/docs/deep-linking/)
- [Configure React Navigation linking](https://reactnavigation.org/docs/configuring-links)

While React Navigation's docs cover the setup for the iOS/Android app, this guide aims to be a complete reference for enabling Universal Linking, including the code needed on the website.

## Video

<video
  src="https://user-images.githubusercontent.com/13172299/167017697-05a47c22-f6c7-49f3-a9a6-cdf6903cd13c.mov"
  style={{ maxWidth: '300px' }}
  controls="controls"
/>

## Overview

1. Set up your React Navigation [linking config](https://reactnavigation.org/docs/configuring-links)
   1. Add [prefixes](https://reactnavigation.org/docs/configuring-links#prefixes) correctly
2. Confirm your `app.json`/`app.config.js` has a [`scheme` property in `apps/expo`](https://reactnavigation.org/docs/deep-linking/#setup-with-expo-projects)
3. Configure [`associatedDomains`](https://docs.expo.dev/versions/latest/config/app/#intentfilters) (iOS) and [`intentFilters`](https://docs.expo.dev/versions/latest/config/app/#intentfilters) (Android) in your `app.json`/`app.config.js` in `apps/expo`
4. **Set up `apple-app-site-association` on your Next.js app.**
   1. This part is unique to this guide.
5. Test it out

_Android instructions for universal linking aren't done yet. I'd accept a PR though._

### 1. Linking config

- [React Navigation Docs](https://reactnavigation.org/docs/configuring-links)
- [Example linking config](https://github.com/nandorojo/solito/blob/master/example-monorepos/blank/packages/app/provider/navigation/index.tsx)
  - Note that this example only has one prefix. If you want Universal Links to work (meaning `yourdomain.com` should open your app), be sure to add the correct [prefixes](https://reactnavigation.org/docs/configuring-links#prefixes).

For example, if your website is `beatgig.com`, you might have something like this in your prefixes:

```ts
import * as Linking from 'expo-linking'

const url = 'beatgig.com'

const config = {
  prefixes: [
    Linking.createURL('/'),
    // https, including subdomains like www.
    `https://${url}/`,
    `https://*.${url}/`,
    // http, including subdomains like www.
    `http://${url}/`,
    `http://*.${url}/`,
  ],

  // ...
}
```

### 2. Scheme

Confirm your `app.json`/`app.config.js` has a [`scheme` property in `apps/expo`](https://reactnavigation.org/docs/deep-linking/#setup-with-expo-projects)

```js
export default {
  scheme: 'solito', // replace with your app scheme
}
```

Your app scheme will let you link into your app. For example, if your scheme is `solito`, then `solito:///` will open the app from your iPhone.

### 3. `associatedDomains` + `intentFilters`

Expo docs:

- [`associatedDomains`](https://docs.expo.dev/versions/latest/config/app/#associateddomains)
- [`intentFilters`](https://docs.expo.dev/versions/latest/config/app/#intentfilters)

Your `app.json`/`app.config.js` will need to specify the domain you want to receive links from.

For example, imagine you want the app to open from `beatgig.com`. Then in `app.config.js`:

```js
const url = 'beatgig.com'

export default {
  ios: {
    // ...other ios properties
    associatedDomains: [`applinks:${url}`],
  },
  android: {
    // ...other android properties
    intentFilters: [
      {
        action: 'VIEW',
        autoVerify: true,
        data: [
          {
            scheme: 'https',
            host: `*.${url}`,
            pathPrefix: '/',
          },
        ],
        category: ['BROWSABLE', 'DEFAULT'],
      },
    ],
  },
}
```

Notice that `ios` has `applinks:` prefixing the URL.

### 4. Apple App Site association

In order to get Universal Links to work, Apple requires that your website has a file at the path `/.well-known/apple-app-site-association` on your URL.

#### 1. Create an API route

Create a file at this exact location in your Solito repo:

`apps/next/pages/api/.well-known/apple-app-site-association.ts`

Paste the contents from the code block below. Your `TEAM_ID` is found on AppStore Connect when you click the icon in the top right.

Your bundle ID will look something like `com.nandorojo.app`. **It must exactly match the `ios.bundleIdentifier` from your `app.config.js`/`app.json` in `apps/expo`.**

```ts
import type { NextApiRequest, NextApiResponse } from 'next'

const BUNDLE_ID = 'YOUR-APPLE-APP-BUNDLE-ID' // replace with your bundle ID
const TEAM_ID = 'YOUR-APPLE-APP-STORE-TEAM-ID' // replace with your Apple Team ID

const association = {
  applinks: {
    apps: [],
    details: [
      {
        appID: `${TEAM_ID}.${BUNDLE_ID}`,
        paths: [
          // this makes every path open your app
          // this is often not desired
          // see the Apple docs to configure this with granularity
          '*',
        ],
      },
    ],
  },
}

export default (_: NextApiRequest, response: NextApiResponse) => {
  return response.status(200).send(association)
}
```

To see more about how to configure your links, read [Apple's docs](https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html).

By default, the above code will make any URL clicked from your phone that matches your domain open the app. To change this, you can edit the `paths` field. For example, you probably don't want someone clicking your marketing page and then deep linking into your app:

```ts
const BUNDLE_ID = 'YOUR-APPLE-APP-BUNDLE-ID'
const TEAM_ID = 'YOUR-APPLE-APP-STORE-TEAM-ID'

const association = {
  applinks: {
    apps: [],
    details: [
      {
        appID: `${TEAM_ID}.${BUNDLE_ID}`,
        // all paths, except for marketing pages
        paths: [
          // all paths, except for marketing pages where the URL starts with /products/
          // order matters! the first matched case will be used
          'NOT /products/*',
          '*',
        ],
      },
    ],
  },
}

export default (_: NextApiRequest, response: NextApiResponse) => {
  return response.status(200).send(association)
}
```

#### 2. Add a redirect

Finally, Apple expects this file to exist at the exact path `yourdomain.com/.well-known/apple-app-site-association`.

Since the file we created above is an API route, we need to create a `redirect` in `next.config.js`:

`apps/next/next.config.js`

```js
const nextConfig = {
  // ...
  async redirects() {
    return [
      {
        source: '/.well-known/:file',
        destination: '/api/.well-known/:file',
        permanent: false,
      },
    ]
  },
}

// ...
```

Your final `next.config.js` file might look like this:

```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack5: true,
  async redirects() {
    return [
      {
        source: '/.well-known/:file',
        destination: '/api/.well-known/:file',
        permanent: false,
      },
    ]
  },
}

const { withExpo } = require('@expo/next-adapter')
const withPlugins = require('next-compose-plugins')
const withTM = require('next-transpile-modules')([
  'solito',
  'dripsy',
  '@dripsy/core',
  'moti',
  'app',
])

module.exports = withPlugins(
  [withTM, [withExpo, { projectRoot: __dirname }]],
  nextConfig
)
```

### 5. Test your app

First, you'll need to make sure you publish your website on the domain you used.

Once it's live, create a new build of your Expo app with `expo run:ios -d`. Plug your iPhone into your computer to test it out. You can also build it with EAS, Expo's cloud build service.

Next, try texting yourself a URL, and see if it deep links.

It's worth noting that Apple caches your website's `apple-app-site-association` for a specific install of the iPhone app. As a result, if you update your Next.js site's `apple-app-site-association` file, it won't be reflected in the iPhone unless you reinstall it.
